线段树是一颗完全二叉树,它的每个结点都表示一条线段,可以用来解决连续区间的动态查询问题. 线段树只支持区间信息可以由子区间信息合并而来的问题(如最值、乘积、区间和等).
- 线段树的结构:一般来说,区间\(~[a,b]~\)的左儿子是\(~[a,m]~\),右儿子是\(~[m+1,b]~\);
- 线段树的空间:若数组长度是\(~N~\),线段树需要的最大空间为\(~4N~\);
- 线段树的效率:由于二叉树的性质,二叉树的操作时间复杂度基本保持在\(~O(\log N)~\).
线段树的操作
线段树的主要操作有:建树、更新、查询.
建树
线段树的构造过程主要是递归构造:如果当前结点的区间左右端点相等,则给该节点赋值;若该结点区间左右端点不相等,则递归构造它的两个子树,构造完毕它的两个子树后再给该节点赋值. 建树的实例代码如下:
1 | int arr[N], tree[N << 2]; |
例如下面的这段序列:
1 | arr[5] = {11, 12, 13, 14, 15}; |
记录其区间和的线段树如下:
1 | ([0, 4]=65) |
记录其区间最大值的线段树如下:
1 | ([0, 4]=15) |
查询
由于单点查询也可以视为左右端点相等的区间查询,故以下只讨论区间查询. 对于区间\(~[a,b]~\),可以从根结点开始,递归地判断查询区间与当前结点的关系. 由线段树的特性可知,查询的过程中,在每一层选择的区间个数不会多余两个(如果一层选择了三个区间,则一定会有两个相邻的区间是同一个结点的儿子,因此这两个区间可以直接合并为它们的父结点区间.) 区间查询的示例代码如下(以记录区间和为例):
1 | // [L, R]表示查询区间,rt表示当前区间标号,[l, r]为当前访问区间 |
复杂度的估计:由于该树共有\(~O(\log N)~\)层,每层最多选择两个结点,故选择的结点个数也是\(~O(\log N)~\),即查询的时间复杂度为\(~O(\log N)~\).
更新
更新是线段树的核心操作,线段树需要维护的一切信息都要由更新操作来体现. 对于更新,一个关键的操作是把儿子的信息合并到父结点上,以求和为例,代码如下:
1 | void push_up(int rt) |
单点更新
单点更新的步骤非常简单:
- 找到需要更新的单点,进行更新操作;
- 利用
push_up()
函数更新相关区间信息.
仍然以区间求和为例,给出如下示例代码:
1 | // idx为更新的下标,val为更新的值,[l, r]为更新区间,rt为更新区间标号 |
区间更新
线段树更新中最难理解的内容就是区间更新. 区间更新需要用到延迟标记,即给每个结点新增一个标记,记录这个结点是否被做过修改. 对于任意区间的修改,我们按照如下方式进行操作:
- 按查询的方式将其划分成线段树中的结点;
- 修改这些结点的信息,并打上标记;
- 在修改和查询的时候,每访问到一个结点,如果该结点有标记,则执行
push_down
; Push_Down
操作:
- 按标记对子结点进行更新;
- 给子结点都打上相同标记;
- 消掉该结点的标记.
实例代码如下:
1 | void update(int L,int R,int c,int l,int r,int rt) |
简化代码
在线段树中频繁用到的就是访问左儿子和访问右儿子两个操作,而查询等操作最常使用的就是根结点,我们可以利用宏定义将其简化:
1 |
基础练习题目
HDU 1166 敌兵布阵
单点修改,区间查询:
1 |
|
HDU 1754 I hate it
单点更新,区间查询:
1 |
|
HDU 1394 Minimum Inversion Number
单点更新,区间查询:
1 |
|
POJ 3468 A Simple Problem with Integers
区间修改,区间求和(注意乘法会爆int):
1 |
|
其他基础题目
- POJ 3667 Hotel:区间更新;
- HDU 1540 Tunnel Warfare:单点更新,查询结点所在区间;
- HDU 2871 Memory Control:与POJ 3667类似;
进阶题目
这类题目或是写起来比较复杂,或是思维上有难度.
BNUOJ 51636 Squared Permutation
一次更新需要更新多个点,不容易刻画更新的操作.
1 |
|
Codeforces gym 101116G Ground Defense
区间更新,实现起来比较麻烦.
1 |
|
HDU 5023 A Corrupt Mayor’s Performance Art
区间修改的又一典型例子.
1 |
|
其他线段树进阶题目
- HDU 3308 LCIS:细节很多,容易错;
- POJ 1436 Horizontally Visible Segments:转化为区间染色;
- HDU 4747 Mex: 区间更新,区间求和;
- HDU 4601 Letter Tree:线段树+字典树;
- Codeforces 258E Little Elephant and Tree:DFS+线段树;
- Codeforces 269D Maximum Waterfall:线段树+dp.